from config import *
from synapse import *

class Neuron:
	x = 0	   				# current membrane potential
	number_of_layer = -1
	number_of_neuron = -1
	reset_time = -1.0

	# constructors
	def __init__(self, config, x = -1, y = -1):
	 	self.cfg = config
	 	self.number_of_layer = x
	 	self.number_of_neuron = y
	 	self.spikes = None 					# spikes generated throughout all life cycle
	 	self.pending_changes = None 		# pending changes of membrane potential
	 	self.pending_terminals = None 		# pending changes of membrane potential
	 	self.pending_synapses = None 		# pending changes of membrane potential
	 	self.ps = None						# predecessors' synapses to this neuron
	 	self.ss = None						# synapses to successors

	def connect_to_next_layer(self, layer):
	 	for neuron in layer:
	 		synapse = Synapse(self.cfg, self, neuron)
	 		self.ss = self.ss or []
	 		self.ss.append(synapse)
	 		neuron.ps = neuron.ps or []
	 		neuron.ps.append(synapse)

	def reset(self):
				#self.ps = self.ps or []
				#print
				#for synapse in self.ps:
				#	synapse.terminals =synapse.terminals or []
				#	for t in synapse.terminals:
				#		if t.width!=self.cfg.tau:
				#			print t.width,

	 			self.x = 0.0
				self.spikes = None
				self.pending_changes = None
				self.pending_terminals = None
				self.pending_synapses = None
				self.reset_time = -1


	#revised v.0.1.2h - 1
	def change_membrane_potential(self, t):
		e = self.cfg.epsilon
		twm = self.cfg.terminal_weight_max
		self.x = 0.0

	 	if self.pending_changes:
		 	window = [(x,self.pending_terminals[nx], self.pending_synapses[nx], nx) for nx, x in enumerate(self.pending_changes)]# if x <= t]
		 	if window:
		 		shift = {}
				for change in window:
					synapse = change[2]
					if not shift.has_key(synapse): shift[synapse] = 0.0
					dt = t - change[0]
					width = change[1].width
					weight = change[1].weight
					#if width!=self.cfg.tau:
					#	print change[1].width
					shift[synapse] += e(dt, width, weight)
					if 0:
						for i in xrange(3):
							m = 1.0 * (i + 1)
							shift[synapse] += e(dt + width * m, width, weight / m ** 0.5)
							shift[synapse] += e(dt - width * m, width, weight / m ** 0.5)
					#print '\t\te',e(delay, width, weight)
					#if self.cfg.debug: self.cfg.log('shift self.x = %1.3f w=%1.3f t=%1.3f dt=%1.3f'%(e(self.cfg.tau + t - change) * self.pending_terminals[change_number].weight,self.pending_terminals[change_number].weight,self.pending_terminals[change_number].delay,t - change))
				for k in shift.keys():
					self.x += min(shift[k],twm)
					#print '\tadd',self.x
					#print '%1.3f' % min(shift[k],self.cfg.terminal_weight_max)

		 	#if self.reset_time != -1:
		 	#	self.x += self.cfg.ireset(t - self.reset_time)

			#debug
			#if self.cfg.debug: self.cfg.log('total self.x = %1.3f'%(self.x)
		#print 'total',self.x
		return self.x

	#revised v.0.1.2h - 1
	def train(self, t):
	 		#debug
	 		#if self.cfg.debug: self.cfg.log('train %f %s %s' % (t, self, self.number_of_neuron))
	 		L = self.cfg.L
	 		twm = self.cfg.terminal_weight_max

	 		if self.ps:
	 			for synapse in self.ps:

					#debug
		 			#if self.cfg.debug: self.cfg.log('input %s' % (synapse.emitter.number_of_neuron))
		 			post_spike = t
	 				pre_spike = max(synapse.emitter.spikes)
	 				delay = post_spike - pre_spike

			 		learning_modifier = synapse.neighborhood(delay)

			 		#print len(synapse.terminals)
				 	for terminal in synapse.terminals:
				 		dt = pre_spike + terminal.delay - post_spike
				 		#debug
				 		#if self.cfg.debug: self.cfg.log('d = %1.3f dt = % + 1.3f L = % + 1.3f w = %1.3f' % (terminal.delay, dt, self.cfg.L(dt), terminal.weight))
				 		# / debug
				 		terminal.weight += L(dt,learning_modifier)
				 		#if not terminal.delay:
				 		#	print dt,terminal.delay,self.cfg.L(dt,len(neighbors))
			 			if terminal.weight > twm: terminal.weight = twm

			 			#print '!',len(synapse.terminals),
					synapse.terminals = [x for x in synapse.terminals if x.weight > 0]
					#print len(synapse.terminals)
					#print
						#print len(synapse.terminals)
					#print 'step'

	def bconjugate_terminal(self, synapse, delay):
			weight = 0
			#print delay
			neighbor = synapse.get_nearest_neigbor(delay)
			if neighbor:
				modifier = min(abs(delay - neighbor.delay) / self.cfg.tau,1.0)
				if modifier < self.cfg.ip:
					return self.cfg.training_positive_window
				#neighbor.width =  self.cfg.tau * modifier
			else:
				modifier = 1.0
			#print len(synapse.terminals)
			synapse.terminals.append(Terminal(synapse, delay, 0, self.cfg.tau * 1.0))
			#print len(synapse.terminals)
		 	#return self.cfg.training_positive_window * modifier

###################################################################################
	 # main functions
	 #revised v.0.1.2h - 1
	def emmit_spike(self, t, force = False):
	 	#if membrane potential is greater then threshold then emmit spike
	 	if self.x >= self.cfg.nu or force:
	 		#register spike at emmiting neuron
		 	self.spikes = self.spikes or []
		 	self.spikes.append(t)
		 	#propogate spike to all successors
		 	if self.ss:
			 	for synapse in self.ss:
			 		neuron = synapse.receiver
			 		neuron.pending_changes = neuron.pending_changes or []
			 		neuron.pending_terminals = neuron.pending_terminals or []
			 		neuron.pending_synapses = neuron.pending_synapses or []
			 		for terminal in synapse.terminals:
			 			neuron.pending_changes.append(t + terminal.delay)
			 			neuron.pending_terminals.append(terminal)
			 			sid = str(synapse.emitter.number_of_neuron) + ' ' + str(synapse.receiver.number_of_neuron)
			 			neuron.pending_synapses.append(sid)

			#reset emmiter's potential
		 	#if not force:
		 	#	self.inhibit(t)

		 		#debug
		 		#if self.cfg.debug: self.cfg.log('spike! %1.3f [%d] %s'%(t, self.number_of_neuron, self), 2)

		 	return True
		return False

	def inhibit(self, t):
		self.reset_time = t

	def inhibit_terminals(self):
		if self.ps:
			for synapse in self.ps:
				for terminal in synapse.terminals:
					terminal.weight += self.cfg.training_negative_window
				synapse.terminals = [x for x in synapse.terminals if x.weight > 0]
	#text output functions
	def __str__(self):
	 	self.ss = self.ss or []
	 	self.ps = self.ps or []
	 	self.spikes = self.spikes or []
	 	queue = []
	 	if self.pending_changes:
	 		for i in self.pending_changes:
	 			queue.append(i)
	 	return '[% .3f | %3d > %5d]' % (self.x, len(self.spikes), len(queue))
